במהלך כתיבת הספר שמסביר oop למתחילים מאפס לא השארתי בצד גם את נושא ה-Type Hinting. אבל נכון לגרסת php 5.5 עדיין אין לשפה תמיכה ב-type hints למשתנים סקאלאריים, כלומר ל- bool, int ו-string. אבל אם אין תמיכה כזאת בשפה, אנחנו יכולים לתכנת אחת כזאת משלנו והנא כיצד:
bool, string ו-int הם לא בדיוק מילים שמורות ואנחנו יכולים לקרוא ככה למשתנים, פונקציות ומחלקות.
בגלל זה, כאשר php נתקלת בקוד כזה
function a(int $b) {}
היא חושבת ש-int זה סוג של מחלקה כלשהי.
ומה היא עושה אם משתנה b הוא בכלל לא מהסוג של אותה המחלקה?
תפיסת שגיאות
php ידועה בעשרות רמות והודעות שגיאות למיניהם. לא פחות חשוב מזה, היא ידועה גם באפשרות לתפוס שגיאות בכל מני דרכים וצורות. אנחנו בעבר כבר דיברנו על טיפול בשגיאות.
שגיאה מסוג "טיפוס לא מתאים" היא עוד אחת המשגיאות של PHP שמטרתן לומר שמשתנה b בכלל לא היה int כמו שציפינו. הטקסט המלא של השגיאה נראה ככה:
Catchable fatal error: Argument 1 passed to function a() must be an instance of int, string given
catchable
המילה הראשונה בהודעת השגיאה היא מילת המפתח.
היא אומרת שזו שגיאה שאפשר לטפל בה, לתקן אותה ולהמשיך בתוכנה כאילו לא קרתה. זה בדיוק מה, שננסה לעשות.
מה שנעשה הוא נגדיר פעולה לטיפול בשגיאות שתבדוק האם המשתנה שהועבר הוא באמת מהסוג המתאים ואם כן, נמשיך בתוכנה כמו שתיכננו:
set_error_handler('handleTypehint');
function handleTypehint($errorLevel, $errorMessage)
{
static $map =
[
'int' => 'integer',
'bool' => 'boolean',
'str' => 'string'
];
// handle only recoverable errors
if ($errorLevel !== E_RECOVERABLE_ERROR)
return false;
// Check this is the right error message we are trying to handle
if(preg_match('/^Argument (\d)+ passed to (?:(\w+)::)?(\w+)\(\) must be an instance of (\w+), (\w+) given/', $errorMessage, $match))
{
$expectedType = $match[4];
$givenType = $match[5];
if($expectedType === $givenType || $map[$expectedType] === $givenType)
return true;
}
return false;
}
function handleTypehint($errorLevel, $errorMessage)
{
static $map =
[
'int' => 'integer',
'bool' => 'boolean',
'str' => 'string'
];
// handle only recoverable errors
if ($errorLevel !== E_RECOVERABLE_ERROR)
return false;
// Check this is the right error message we are trying to handle
if(preg_match('/^Argument (\d)+ passed to (?:(\w+)::)?(\w+)\(\) must be an instance of (\w+), (\w+) given/', $errorMessage, $match))
{
$expectedType = $match[4];
$givenType = $match[5];
if($expectedType === $givenType || $map[$expectedType] === $givenType)
return true;
}
return false;
}
בוא נבין מה הקוד הזה עושה
ראשית התופס שגיאות האוניברסלי שלנו הולך לתפוס את כל השגיאות. גם כאלה שנבעו בכלל לא מהעניין עם טיפוסי הנתונים או כאלה שאנחנו בכלל לא יכולים לטפל בהם. בשביל לא לבזבז סיבובי מעבד אנחנו נדלג עליהם.
שנית, באמצעות ביטוי רגולרי אנחנו נהרוג שני ציפורים במקה:
גם נוודא שהשגיאה שנתפסה קשורה לבעיית הטיפוסים הלא נכונים
וגם נקבל לתוך שני משתנים את הסוג שהפונקציה מצפה לקבל ואת הסוג שהפונקציה קיבלה בפועל.
לנו נשאר רק להשוות את שני המשתנים האלה ולבדוק האם הסוג שהועבר זהה לסוג שציפו לקבל ואם כן, להמשיך הלאה על ידי החזרת true. אם לא, יוחזר false וphp תדע שמשהו הלך לא כמו שצריך.
בתור בונוס: היות וברוב שפות התכנות משתמשים בקיצורים, כמו int במקום integer ו-bool במקום boolean וב-php לא — אנחנו נמפה את הקיצורים אל שמם המלא בשביל ההשוואה. ואם בהודעת השגיאה כתוב expected int, given integer אנחנו בכל זאת נוכל לתפעל את המצב.
והנא דוגמה ב-phplive
disclosure
כמו שמיכאל ויהב כתבו למטה (ובצדק מוחלט) יש לגישה הזו שני חסרונות:
א. יש ירידה בביצועים (עד כמה היא משמעוטית אתה מוזמן להחליט לבד)
ב. לממש "הרחבות" לשפה בתוך הקוד שלך זה רעיון רע מאוד. גם מבחינת תאימות של עורכים למיניהם שלא יידעו איך לאכול את זה ובכל זאת יציגו שגיאות, גם מבחינת תאימות לגרסאות באות / קודמות ובכלל קונצפטואלית זה לא רעיון נכון.
הפוסט הזה מיועד יותר להרחבת אופקים מאשר להכנסת הקוד לפרוייקט שלך. אם אתה לא מבין עד הסוף מה בדיוק הולך פה ואיך PHP תתנהג במקרים שונים, אני לא ממליץ להשתמש בקוד הזה בפורדאקשן.
תגובות לכתבה:
it does a cool hack, but its pretty much useless in real life applications
nice tutorial =)
Thx.
Do you see a reason not to use it in production or real life apps?
It definitely has a certain small penalty on performance, but so does a normal class based type hinting, while this one has only a slight overhead on top.
first of all, yeah, the performance penalty.
http://phpguide.co.il/phplive?code=858
second I (at least) believe that you shouldn't try to re-invent php with adding stuff like this.
plus your ide will annoy you for using undefined class when you write scalar type hint or when calling some method it will say that the argument is not instance of int/string/bool
its the same as:
https://github.com/nikic/scalar_objects
making scalars act like object seems pretty nice, like ruby & python does,
but you won't really compile this extension in production.
in nutshell it is a cool helper, but I don't see myself adding this to my applications, I prefer that php will support it natively (yeah, right)
The idea is great, though I, too, believes that this is not a good idea to use it.
Firstly, this way of hack will have much impact on performance. That's since a huge-scale application will use this hack at least 100 times in a page and each time you'll send them to the PHP error mechanism.
Secondly, You're using a hack, a "magic", to solve things and it may confuse other developers. Regards to that, even the IDE won't accept this kind of thing.
Finally, I don't believe that feature should be implemented in userland and... to be honest, I don't think that it should be implemented at all in PHP since it doesn't follow the concepts behind PHP. Thats being said, as a .NET developer (and so ASP.NET particulary) - I do agree that there're pros to scalar-type-hinting and they can avoid some confusions for developers.
בתור פתרון זה פתרון נחמד, אבל בלתי שימושי בעליל, ואף מזיק. לא מומלץ בכלל, אבל מחשבה יפה :)